use std::{collections::HashSet, time::Duration};

use anyhow::Ok;
use chrono::{DateTime, Months, Utc};
use gitlab::{
    api::{
        common, paged,
        projects::{self, repository::commits},
        AsyncQuery, Pagination,
    },
    AsyncGitlab, GitlabBuilder,
};
use sea_orm::prelude::DateTimeUtc;

use super::bean;

/// 创建 client
pub async fn new_gitlab_client(
    host: &str,
    token: &str,
    is_https: bool,
) -> anyhow::Result<AsyncGitlab> {
    let mut builder = GitlabBuilder::new(host, token);
    builder.cert_insecure();
    if !is_https {
        builder.insecure();
    }
    Ok(builder.build_async().await?)
}

pub struct ProjectService {
    client: AsyncGitlab,
    query_last_id: i64,
    pagination: Pagination,
}

impl ProjectService {
    pub fn new(client: AsyncGitlab) -> Self {
        Self {
            client,
            query_last_id: 0,
            pagination: Pagination::Limit(100),
        }
    }

    pub async fn query(&mut self, last_project_id: i64) -> anyhow::Result<Vec<bean::Project>> {
        let endpoint = projects::Projects::builder()
            .id_after(last_project_id as u64)
            .sort(common::SortOrder::Ascending)
            .order_by(projects::ProjectOrderBy::Id)
            .build()?;
        let result: Vec<_> = paged(endpoint, self.pagination)
            .query_async(&self.client)
            .await?;

        Ok(result)
    }

    pub async fn next_query(&mut self) -> anyhow::Result<Vec<bean::Project>> {
        let result = self.query(self.query_last_id).await?;
        if let Some(last_row) = result.last() {
            self.query_last_id = last_row.id
        }
        Ok(result)
    }
}

pub async fn branch_list(project_id: u64, client: &AsyncGitlab) -> anyhow::Result<Vec<String>> {
    let endpoint = projects::repository::branches::Branches::builder()
        .project(common::NameOrId::Id(project_id))
        .build()?;
    let branches: Vec<bean::Branch> = endpoint.query_async(client).await?;
    let result: Vec<String> = branches.iter().map(|item| item.name.to_owned()).collect();
    Ok(result)
}

pub struct CommitService<'a> {
    client: &'a AsyncGitlab,
    project: &'a dao::project_info::Model,
    after_date: DateTime<Utc>,
    before_date: DateTime<Utc>,
    pub current_after_date: Option<DateTime<Utc>>,
    pub current_before_date: Option<DateTime<Utc>>,
    pagination: Pagination,
    step_month: Months,
    branch_list: Vec<String>,
}

impl<'a> CommitService<'a> {
    pub async fn new(
        client: &'a AsyncGitlab,
        project: &'a dao::project_info::Model,
        after_date: Option<DateTime<Utc>>,
        before_date: Option<DateTime<Utc>>,
    ) -> anyhow::Result<Self> {
        let after_date = after_date.unwrap_or(DateTimeUtc::from_naive_utc_and_offset(
            project.create_time.unwrap(),
            Utc,
        ));
        let before_date = before_date.unwrap_or(Utc::now());
        // 查询分支
        let branch_list = branch_list(project.project_id as u64, &client).await?;

        Ok(Self {
            client,
            project,
            after_date,
            before_date,
            current_after_date: None,
            current_before_date: None,
            pagination: Pagination::All,
            step_month: Months::new(1),
            branch_list,
        })
    }

    pub async fn retry_query(&mut self) -> anyhow::Result<Vec<bean::Commit>> {
        tryhard::retry_fn(|| CommitService::query(self))
            .retries(3)
            .fixed_backoff(Duration::from_secs(1))
            .await
    }

    pub async fn next_query(&mut self) -> anyhow::Result<Option<Vec<bean::Commit>>> {
        if self.next_date() {
            let r = self.retry_query().await?;
            Ok(Some(r))
        } else {
            Ok(None)
        }
    }

    pub async fn query(&self) -> anyhow::Result<Vec<bean::Commit>> {
        let mut result = Vec::new();
        let mut set = HashSet::new();
        for branch in &self.branch_list {
            let endpoint = commits::Commits::builder()
                .project(gitlab::api::common::NameOrId::Id(
                    self.project.project_id as u64,
                ))
                .since(self.current_after_date.unwrap())
                .until(self.current_before_date.unwrap())
                .ref_name(branch.to_owned())
                .all(true)
                .with_stats(true)
                .build()?;

            let row: Vec<bean::Commit> = paged(endpoint, self.pagination)
                .query_async(self.client)
                .await?;

            // 去重
            for mut ele in row {
                if set.insert(ele.id.to_owned()) {
                    ele.branch = Some(branch.to_owned());
                    result.push(ele);
                }
            }
        }

        Ok(result)
    }

    fn next_date(&mut self) -> bool {
        if let Some(current_before_date) = self.current_before_date {
            if current_before_date >= self.before_date {
                return false;
            }
        }
        self.current_after_date = self.current_before_date.or(Some(self.after_date));
        self.current_before_date = match self.current_before_date {
            Some(date) => self.before_date(date.checked_add_months(self.step_month)),
            None => self.before_date(self.after_date.checked_add_months(self.step_month)),
        };

        true
    }

    fn before_date(&self, current_date: Option<DateTime<Utc>>) -> Option<DateTime<Utc>> {
        let r = if let Some(date) = current_date {
            if date <= self.before_date {
                date
            } else {
                self.before_date
            }
        } else {
            self.before_date
        };
        Some(r)
    }
}
